﻿using System;
using System.Threading.Tasks;

namespace Acoris.Markdown
{
#if NET_UWP

    using Windows.Storage;

#elif NET_WPF

    using System.IO;

#endif

    #region State Enum Defination

    internal enum AmlDocumentState
    {
        Empty,

        // [
        CollectionStart,

        CollectionName,

        // ]
        CollectionEnd,

        // @
        ItemStart,

        ItemProperty,

        // {
        CollectionBodyStart,

        // {
        ItemBodyStart,

        ItemValue,

        // }
        ItemBodyEnd,

        // }
        CollectionBodyEnd,

        Text
    }

    #endregion

    /// <summary>
    /// 表示一个Aml文档读取器
    /// </summary>
    public sealed class AMLReader:TextReader
    {


        /*
         * 该类主要负责生成Aml文档结构
         */

        #region DocumentStream Defination

        internal class DocumentStream
        {
            string m_document;
            int m_linePosition;
            int m_column;
            int m_offset;

            public DocumentStream(string document)
            {
                m_document = document;
                m_offset = m_column = m_linePosition = 0;
            }

            public bool Move()
            {
                if (m_offset < m_document.Length) {
                    m_offset++;
                    return true;
                }
                return false;
            }

            public char Current {
                get { return m_document[m_offset]; }
            }

            public int LinePosition {
                get { return m_linePosition; }
            }

            public int ColumnPosition {
                get { return m_column; }
            }

            public bool EndOfStream {
                get { return m_offset == m_document.Length; }
            }

            public int Offset {
                get { return m_offset; }
            }

            public string Document {
                get { return m_document; }
            }

            public override string ToString()
            {
                return $"offset:{m_offset},char:{Current},length:{m_document.Length}";
            }
        }

        #endregion

        async public override Task<object> LoadAsync(string fileName)
        {
#if NET_UWP
            var storageFile = await StorageFile.GetFileFromPathAsync(fileName);
            var document = await FileIO.ReadTextAsync(storageFile);
            return Load(document);
#elif NET_WPF
            var stream = new StreamReader(new FileStream(fileName, FileMode.Open));
            var document = await stream.ReadToEndAsync();
            return Load(document);
#endif
        }


        internal object Load(string document)
        {
            var stream = new DocumentStream(document);
            return Parse(stream);
        }

        #region Parse 

        // remember:
        // we should record the start offset ,length offset,name offset, name length,value offset ,value length
        // 1.under CollectionStart or ItemStart State we should push a parseframe into parseFrameStack
        // because we need to record tag offset
        // 2.under CollectionNameState or ItemPropertyState we also need push a parseframe,specially
        // under CollectionNameState we need push to the Scope
        // 3.under CollectionBodyStartState or ItemValueState we need push a parseframe
        // 4.under CollectionBodyEndState or ItemEndState we should pop all record parseFrame
        // for ItemEndState we should pop ItemValue,ItemProperty,ItemOffset and compute 
        // total length.
        // for CollectionBodyEndState we should pop AmlDocumentState,CollectionValue,CollectionName,
        // Offset,and compute total length

        /// <summary>
        /// 解析文档
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        internal object Parse(DocumentStream stream)
        {
            var context = new AmlParseContext();
            var node = (AMLParseNode)null;


            for (; !stream.EndOfStream;) {
                switch (context.State) {
                    case AmlDocumentState.CollectionBodyEnd:
                        CollectionBodyEndState(stream, context);
                        break;
                    case AmlDocumentState.CollectionBodyStart:
                        CollectionBodyStartState(stream, context);
                        break;
                    case AmlDocumentState.CollectionEnd:
                        CollectionEndState(stream, context);
                        break;
                    case AmlDocumentState.CollectionName:
                        CollectionNameState(stream, context);
                        break;
                    case AmlDocumentState.CollectionStart:
                        CollectionStartState(stream, context);
                        break;
                    case AmlDocumentState.Empty:
                        EmptyState(stream, context);
                        break;
                    case AmlDocumentState.ItemBodyEnd:
                        ItemBodyEndState(stream, context);
                        break;
                    case AmlDocumentState.ItemBodyStart:
                        ItemBodyStartState(stream, context);
                        break;
                    case AmlDocumentState.ItemProperty:
                        ItemPropertyState(stream, context);
                        break;
                    case AmlDocumentState.ItemStart:
                        ItemStartState(stream, context);
                        break;
                    case AmlDocumentState.ItemValue:
                        ItemValueState(stream, context);
                        break;
                    case AmlDocumentState.Text:
                        TextState(stream, context);
                        break;
                }
            }

            node = context.Root;
            if (context.Scope != null) {
                context.EndScope();
            }
            context.Reset();
            return node;
        }

        // do:
        // find first control-character
        // if @ changed AmlDocumentState
        // if [ changed AmlDocuemntState
        internal void EmptyState(DocumentStream stream, AmlParseContext context)
        {
            // 寻找第一个 [标记 或者 @标记
            var symbol = char.MaxValue;
            for (; !stream.EndOfStream; stream.Move()) {
                symbol = stream.Current;
                if (symbol == '[' || symbol == '@') {
                    context.PushState(context.State);
                    break;
                }
            }

            if (stream.EndOfStream) {
                return;
            }

            if (symbol == '[') {
                context.State = AmlDocumentState.CollectionStart;
            }
            else if (symbol == '@') {
                context.State = AmlDocumentState.ItemStart;
            }
        }

        // do:
        // move stream
        // changed AmlDocumentState
        internal void ItemStartState(DocumentStream stream, AmlParseContext context)
        {
            var symbol = char.MaxValue;
            var offset = stream.Offset;
            for (; !stream.EndOfStream; stream.Move()) {
                symbol = stream.Current;
                if (symbol != '\t' &&
                    symbol != '\x20' &&
                    symbol != '{') {
                    break;
                }
            }

            if (stream.EndOfStream) {
                return;
            }

            context.ParseFrameStack.Push(new AmlParseFrame(ParseFrameType.Offset, offset, 0));
            context.State = AmlDocumentState.ItemProperty;
        }

        // do:
        // record offset
        // record length
        // create a new parseFrame into ParseFrameStack
        // changed AmlDocumentState
        internal void ItemPropertyState(DocumentStream stream, AmlParseContext context)
        {
            var symbol = char.MaxValue;
            var offset = stream.Offset;
            var length = 0;
            var parseFrameStack = context.ParseFrameStack;

            for (; !stream.EndOfStream; stream.Move(), length++) {
                symbol = stream.Current;

                if (symbol == '{') {
                    // 遇到 ItemBodyStart
                    // 则返回
                    break;
                }
            }

            // 将ItemProperty作为ParseFrame添加到解析栈中

            parseFrameStack.Push(new AmlParseFrame(ParseFrameType.ItemProperty, offset, length));
            context.State = AmlDocumentState.ItemBodyStart;
        }

        // do:
        // move stream
        // changed AmlDocumentState
        internal void ItemBodyStartState(DocumentStream stream, AmlParseContext context)
        {
            if (stream.Current == '{') {
                stream.Move();
            }

            context.State = AmlDocumentState.ItemValue;
        }

        // do:
        // record the offset;
        // move stream if current not '{'
        // record the length
        // create a new parseFrame into ParseFrameStack
        // changed AmlDocumentState
        internal void ItemValueState(DocumentStream stream, AmlParseContext context)
        {
            var symbol = char.MaxValue;
            var offset = stream.Offset;
            var length = 0;

            for (; !stream.EndOfStream; stream.Move(), length++) {
                symbol = stream.Current;
                if (symbol == '}') {
                    break;
                }
            }

            var frame  = new AmlParseFrame(ParseFrameType.ItemValue,offset,length);
            context.ParseFrameStack.Push(frame);
            context.State = AmlDocumentState.ItemBodyEnd;
        }

        // do:
        // pop offsetFrame,propertyFrame,valueFrame
        // create a new AmlItemNode
        // append to the scope node's children collection
        // pop documentState
        internal void ItemBodyEndState(DocumentStream stream, AmlParseContext context)
        {

            // create value from valueFrame
            var valueFrame = context.ParseFrameStack.Pop();

            // create property from propertyFrame
            var propertyFrame = context.ParseFrameStack.Pop();

            var offset = context.ParseFrameStack.Pop();

            var node = new AMLItem(context.Scope,
                offset.Offset,stream.Offset - offset.Offset,
                stream.Document,
                propertyFrame.Offset,propertyFrame.Length,
                valueFrame.Offset,valueFrame.Length);

            if (valueFrame.Type != ParseFrameType.ItemValue || propertyFrame.Type != ParseFrameType.ItemProperty) {
                throw new InvalidOperationException();
            }

            context.Scope
                   .Children
                   .Add(node);
            context.PopState();
            // 尝试移动
            if (stream.Current == '}') {
                stream.Move();
            }
        }

        // do:
        // record offset
        // move stream
        // create a new parseFrame into ParseFrameStack
        internal void CollectionStartState(DocumentStream stream, AmlParseContext context)
        {

            if (stream.Current == '[') {
                stream.Move();
            }
            var offset = stream.Offset;
            context.ParseFrameStack.Push(new AmlParseFrame(ParseFrameType.Offset, offset, 0));
            context.State = AmlDocumentState.CollectionName;
        }

        // do:
        // record offset
        // record length
        // create a new parseFrame into ParseFrameStack
        // changed AmlDocumentState
        internal void CollectionNameState(DocumentStream stream, AmlParseContext context)
        {
            var symbol = char.MaxValue;
            var offset = stream.Offset;
            var length = 0;
            while (!stream.EndOfStream) {
                symbol = stream.Current;
                if (symbol == ']') {
                    break;
                }
                stream.Move();
                length++;
            }

            var offsetFrame = context.ParseFrameStack.Pop();
            var node = new AMLCollection(context.Scope,
                                         offsetFrame.Offset,0,
                                         stream.Document,
                                         offset,length,
                                         0,0);
            context.BeginScope(node);
            context.State = AmlDocumentState.CollectionEnd;
        }

        // do:
        // find the first '{'
        // record offset
        // changed AmlDocumentState
        internal void CollectionEndState(DocumentStream stream, AmlParseContext context)
        {
            for (; !stream.EndOfStream; stream.Move()) {
                if (stream.Current == '{') {
                    break;
                }
            }
            context.State = AmlDocumentState.CollectionBodyStart;
        }

        // do:
        // move stream
        // beginScope
        // pop AmlDocumentState
        //
        internal void CollectionBodyStartState(DocumentStream stream, AmlParseContext context)
        {
            stream.Move();// skip {

            var symbol = char.MaxValue;
            var offset = stream.Offset;

            for (; !stream.EndOfStream; stream.Move()) {
                symbol = stream.Current;
                if (symbol == '[') {
                    context.PushState(context.State);
                    stream.Move();
                    context.State = AmlDocumentState.CollectionStart;
                    goto ContentOffset;
                }
                else if (symbol == '@') {
                    context.PushState(context.State);
                    stream.Move();
                    context.State = AmlDocumentState.ItemStart;
                    break;
                }
                else if (symbol == '}') {
                    stream.Move();
                    context.State = AmlDocumentState.CollectionBodyEnd;
                    break;
                }
                else if (symbol != '\t' &&
                        symbol != '\x20' &&
                        symbol != '\r' &&
                        symbol != '\n') {
                    context.PushState(context.State);
                    context.State = AmlDocumentState.Text;
                    goto ContentOffset;
                }
            }

            return;

            // 下面的代码只有非CollectionBodyEnd状态才能执行
            ContentOffset:

            var parseStack = context.ParseFrameStack;
            var frame = new AmlParseFrame(ParseFrameType.Offset,stream.Offset - 1,0);
            parseStack.Push(frame);
        }

        // do:
        // changed AmlDocumentState
        internal void TextState(DocumentStream stream, AmlParseContext context)
        {
            var symbol = char.MaxValue;
            var offset = stream.Offset;
            var length = 0;
            for (; !stream.EndOfStream; stream.Move(), length++) {
                symbol = stream.Current;
                if (symbol == '}') {
                    break;
                }
            }
            
            context.Scope.Content = stream.Document.Substring(offset, length);
            context.PopState();
        }

        // do:
        // move stream
        // endScope
        // popState
        internal void CollectionBodyEndState(DocumentStream stream, AmlParseContext context)
        {
            if (stream.Current == '}') {
                stream.Move();
            }

            var offsetFrame = context.ParseFrameStack.Pop();
            var totalLength = stream.Offset - offsetFrame.Offset;
            var scope = context.Scope as AMLCollection;
            scope.TextOffset = offsetFrame.Offset;
            scope.TextLength = totalLength;

            context.EndScope();
            context.PopState();
        }

        #endregion
    }
}
